// Uncomment this line to get debugging to come out while using NUnit.  This seems
// to be the only way to get the debugging code to work because Nunit keeps dynamically
// loading and unloading our assembly and there is no configuration file to place
// our diagnostic switch settings.
//#define InitialSettingVerbose

using System;
using System.Collections;
using System.Diagnostics;
using System.IO;

namespace Pegasus.Diagnostics
{
	/// <summary>
	/// This class extends the TraceSwitch class found in the System.Diagnostics
	/// to allow the code to dynamically change the trace level of different
	/// classes at runtime.  All ExTraceSwitch objects that are initialized
	/// with the same switch name point to the single reference of that object.
	/// This allows the code to change a single setting and all classes that use
	/// that extended trace switch object will react to the change.
	/// </summary>
	/// <remarks>
	/// This class is set to force the allocation of any new objects through
	/// a class factory method.  This allows the code to keep track of all
	/// the ExTraceSwitch objects that are allocated.  Then when the static
	/// methods are called to change a switch value, the code can walk
	/// a collection of objects, find the correct switch and change the
	/// trace level.  For example, the following code snippets show hot this
	/// extended trace switch object can be used in a class:
	/// <code>
	/// -- First, In your class declare a trace switch that looks like:
	///	static private ExTraceSwitch traceSwitch = ExTraceSwitch.AllocateTraceSwitch(
	/// "[namespace].[class name]",
	/// "[Short description of the class]" );
	/// 
	/// -- Next, use the trace switch in the Trace.WriteLineIf() method (note:  this will
	/// -- make the call to evaluate the boolean.  It is NOT the most efficient way
	/// -- to place Trace statements.  I use this form for Warnings and Errors.
	/// 
	/// Trace.WriteLineIf( traceSwitch.TraceWarning,
	///     "MethodName() [put your favorite trace message here]." );
	/// 
	/// -- Alternatively, to insure FASTER code execution wrap any Verbose or Info
	/// -- messages in an if statement that uses the Trace.WriteLine() if the value
	/// -- is set.
	/// 
	/// if( traceSwitch.TraceVerbose )
	///     Trace.WriteLine( "MethodName() [put your favorite trace message here]." );
	///     
	/// -- Finally, ALL serious exceptions should be dumped using the System.Diagnostics
	/// -- Trace facility.  Here are two examples.  The first example dumps the message
	/// -- and the stack frame in a single trace line.  The second uses two trace messages
	/// -- to accomplish the same thing.
	/// 
	/// -- Example 1:
    ///	catch( Exception ex )
	/// {
	///     Trace.WriteLine( "MethodName() [put your favorite exception message here].  Exception: "
	///         + ex.Message + "\n" + ex.StackTrace );
	/// }
	/// 
	/// -- Example 2:
	///	catch( Exception ex )
	/// {
	///     Trace.WriteLine( "MethodName() [put your favorite exception message here].  Exception: "
	///         + ex.Message );
	///     Trace.WriteLine( ex.StackTrace );
	/// }
	/// </code>
	/// Please note, you have the option to use TraceSwitch objects however you need them.  You might
	/// want to organize your tracing so that you can follow a transaction.  This could mean setting the
	/// switch names to:
	/// <code>
	/// "Transaction.OrderEntry"
	/// "Transaction.ShoppingCart"
	/// "Transaction.Checkout"
	/// "Transaction.CreditCardProcessing"
	/// etc.
	/// </code>
	/// The above hierarchy would allow an application to trace the order entry or credit card processing
	/// areas or simply set all switches that start with "Transaction" to a specific trace level.  You
	/// are free to organize your switch names in whatever fashion suits your needs.
	/// </remarks>
	[Obsolete( "Use Log4Net API in place of TraceSwitches", false )] 
	public class TraceSwitchEx : TraceSwitch
	{
		/// <summary>
		/// This list holds the TraceSwitchEx objects that have been allocated.
		/// </summary>
		private static SortedList  s_traceSwitchList = SortedList.Synchronized( new SortedList() );

		/// <summary>
		/// By convention, when an ExTraceSwitch is used in a class, all objects
		/// of that class type will behave according to that particular
		/// ExTraceSwitch's settings.  If, however, an instance name is
		/// defined for the ExTraceSwitch, then only the object with that
		/// instance name will trace. 
		/// </summary>
		/// <example>
		/// Trace.WriteLineIf( ( traceSwitch.InstanceName == null 
		///      || traceSwitch.InstanceName.Equals( this.instanceName ) )
		///      &amp;&amp; traceSwitch.Verbose, "yada yada yada..." );
		/// </example>
		private string m_instanceName = null;

		/// <summary>
		/// The default value to set when new trace switches are added to the system.
		/// </summary>
		private static TraceLevel s_defaultLevel = TraceLevel.Off;

		/// <summary>
		/// Private constructor that is called from the public class
		/// factory method to create a new ExTraceSwitch.  This constructor
		/// passes the parameters to the TraceSwitch base class and
		/// returns.
		/// </summary>
		/// <param name="SwitchName">
		/// The display name that will be passed to the TraceSwitch base class.
		/// </param>
		/// <param name="Description">
		/// The description that will be passed to the TraceSwitch base class.
		/// </param>
		private TraceSwitchEx( string SwitchName, string Description ) : 
			base( SwitchName, Description )
		{
		}

		/// <summary>
		/// Gets or sets the InstanceName associated with this TraceSwitch.
		/// The default value is null.
		/// </summary>
		public string InstanceName
		{
			get
			{ 
				return m_instanceName; 
			}

			set
			{ 
				m_instanceName = value; 
			}
		}

		/// <summary>
		/// Get a listed of the registed trace switches
		/// </summary>
		public static SortedList TraceSwitches
		{
			get
			{
				return (SortedList) s_traceSwitchList.Clone();
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="traceLevel"></param>
		/// <param name="instance"></param>
		/// <returns></returns>
		public bool TraceInstance( TraceLevel traceLevel, string instance )
		{
			if( traceLevel == Level && 
				( m_instanceName == null || m_instanceName == instance ) )
			{
				return true;
			}

			return false;
		}

		/// <summary>
		/// This is a class factory method that will search the hash table
		/// for an ExTraceSwitch with the specified display name, if we find it
		/// we return the object.  If one isn't there, a new one is allocated,
		/// placed in the hash table, and returned to the caller.  This method
		/// is synchronized (locked on the ExTraceSwitch class type) to protect
		/// the hash table operations.
		/// </summary>
		/// <param name="switchName">The name of the ExTraceSwitch.</param>
		/// <param name="description">The description for the trace switch object.</param>
		/// <returns>
		/// A valid TraceSwitch object is returned to the caller.  Either
		/// one that was found in the hash table or a new one that was just
		/// allocated.
		/// </returns>
		public static TraceSwitchEx AllocateTraceSwitch( string switchName, string description )
		{
			TraceSwitchEx traceSwitch = (TraceSwitchEx) s_traceSwitchList[ switchName ];
			if( traceSwitch == null )
			{
				traceSwitch = new TraceSwitchEx( switchName, description );
				traceSwitch.Level = s_defaultLevel;

				s_traceSwitchList.Add( switchName, traceSwitch );
			}

			return traceSwitch;
		}

		/// <summary>
		/// Use this static method if you want to remove a Trace Switch from
		/// control of this object.  This should be use very INFREQUENTLY.
		/// It will remove the item from the hash table no matter how many
		/// references to the trace switch have been handed out.  For the
		/// most part, simply allocate a trace switch as a private static item
		/// and forget about it.  This method is provided for the very few cases
		/// when it really should be removed once you are finished with it.
		/// An ArgumentException is thrown if the switch is not found in the
		/// hash table
		/// </summary>
		/// <param name="switchName">The name of the switch to remove.</param>
		public static void RemoveTraceSwitch( string switchName )
		{
			// Remove the item.
			s_traceSwitchList.Remove( switchName );
		}  

		/// <summary>
		/// Use this static method to get the current value of an ExTraceSwitch object
		/// by passing in the switch name.  This method is synchronized (locked on
		/// the ExTraceSwitch class type) to protect the hash table operations.
		/// </summary>
		/// <param name="switchName">The switch name to get.</param>
		/// <returns>The TraceSwitch for the given name.</returns>
		public static TraceSwitchEx GetTraceSwitch( string switchName )
		{
			return (TraceSwitchEx) s_traceSwitchList[ switchName ];
		}
		
		/// <summary>
		/// Use this static method to set the level of an ExTraceSwitch object by name.  This
		/// method is synchronized (locked on the ExTraceSwitch class type) to protect the
		/// hash table operations.
		/// </summary>
		/// <param name="switchName">The name of the switch.</param>
		/// <param name="level">The level of the TraceSwitch.</param>
		public static void SetTraceSwitch( string switchName, TraceLevel level )
		{
			TraceSwitchEx traceSwitch = (TraceSwitchEx) s_traceSwitchList[ switchName ];
			if( traceSwitch != null )
			{
				traceSwitch.Level = level;
			}
			else
			{
				throw new ArgumentException( "There is no Trace Switch with the specified name", switchName );
			}
		}

		/// <summary>
		/// Set all registed trace switches to the given level.
		/// </summary>
		/// <param name="level">The level of the TraceSwitch.</param>
		public static void SetAllTraceSwitches( TraceLevel level )
		{
			lock( s_traceSwitchList.SyncRoot )
			{
				foreach( TraceSwitchEx traceSwitch in s_traceSwitchList.Values )
				{
					traceSwitch.Level = level;
				}
			}
		}

		/// <summary>
		/// Set the default value for all the trace switches.
		/// </summary>
		/// <param name="level"></param>
		public static void SetDefaultTraceLevel( TraceLevel level )
		{
			s_defaultLevel = level;
			SetAllTraceSwitches( level );
		}

		/// <summary>
		/// Use this method to set an entire switch "hierarchy" to a specified
		/// trace level.  This method takes the SwitchHierarchy parameter passed
		/// in and sets any trace switch whose key (display name) starts with
		/// that name.  For example, if the parameter is set to "MFN.Shared.Utilities"
		/// then both the "MFN.Shared.Utilities.Commands", "MFN.Shared.Utilities.WorkerThreads",
		/// etc. will have thier trace level modified.
		/// </summary>
		/// <param name="switchHierarchy">
		/// The name of the hierarchy to change.  To change all the switches in the MFN
		/// Shared namespace, pass "MFN.Shared".
		/// </param>
		/// <param name="level">The level of the TraceSwitch.</param>
		/// <returns>
		/// Returns the number of trace switch objects that were changed to the new level
		/// </returns>
		public static int SetTraceSwitchHierarchy( string switchHierarchy, TraceLevel level )
		{
			int count = 0;

			lock( s_traceSwitchList.SyncRoot )
			{
				foreach( DictionaryEntry entry in s_traceSwitchList )
				{
					string name = (string) entry.Key;
					if( name.StartsWith( switchHierarchy ) )
					{
						TraceSwitchEx traceSwitch = (TraceSwitchEx) entry.Value;
						traceSwitch.Level = level;
						count++;
					}
				}
			}

			return count;
		}

		/// <summary>
		/// Called to convert a level enumeration value to a string.
		/// </summary>
		/// <param name="level">The level to convert.</param>
		/// <returns>
		/// The string representation of the level.
		/// </returns>
		public static string GetLevelName( TraceLevel level )
		{
			switch( level )
			{
				case TraceLevel.Off:
					return "Off";

				case TraceLevel.Verbose:
					return "Verbose";

				case TraceLevel.Info:
					return "Info";

				case TraceLevel.Warning:
					return "Warn";

				case TraceLevel.Error:
					return "Error";
			}

			return "Unknown";
		}

		/// <summary>
		/// Uses the given string to return the trace level enum
		/// </summary>
		/// <param name="levelName">Name of the enum param</param>
		/// <returns>The trace level that matches</returns>
		public static TraceLevel GetLevel( string levelName )
		{
			TraceLevel level = TraceLevel.Off;

			switch( levelName.ToLower() )
			{
				case "off":
					level = TraceLevel.Off;
					break;

				case "verbose":
					level = TraceLevel.Verbose;
					break;

				case "info":
					level = TraceLevel.Info;
					break;

				case "warn":
					level = TraceLevel.Warning;
					break;

				case "error":
					level = TraceLevel.Error;
					break;

				default:
					throw new ArgumentException( "Unknown trace level", levelName );
			}

			return level;
		}
	}
}
